package com.lframework.jh.push.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lframework.jh.push.core.components.security.SecurityConstants;
import com.lframework.jh.push.core.components.security.UserTokenResolver;
import com.lframework.jh.push.core.constants.StringPool;
import com.lframework.jh.push.core.exceptions.impl.DefaultClientException;
import com.lframework.jh.push.core.impl.BaseMpServiceImpl;
import com.lframework.jh.push.core.utils.*;
import com.lframework.jh.push.service.annotations.OpLog;
import com.lframework.jh.push.service.dto.MenuDto;
import com.lframework.jh.push.service.entity.SysMenu;
import com.lframework.jh.push.service.enums.OpLogType;
import com.lframework.jh.push.service.enums.SysMenuComponentType;
import com.lframework.jh.push.service.enums.SysMenuDisplay;
import com.lframework.jh.push.service.mappers.SysMenuMapper;
import com.lframework.jh.push.service.service.SysMenuService;
import com.lframework.jh.push.service.utils.OpLogUtil;
import com.lframework.jh.push.service.vo.menu.CreateSysMenuVo;
import com.lframework.jh.push.service.vo.menu.SysMenuSelectorVo;
import com.lframework.jh.push.service.vo.menu.UpdateSysMenuVo;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.*;
import java.util.regex.Pattern;

/**
 * <p>
 * 系统菜单 服务实现类
 * </p>
 *
 * @author zmj
 * @since 2021-05-10
 */
@Service
public class SysMenuServiceImpl extends BaseMpServiceImpl<SysMenuMapper, SysMenu> implements
        SysMenuService {

    private String tokenKey = "X-Auth-Token";

    @Autowired
    private UserTokenResolver userTokenResolver;

    @Override
    public List<SysMenu> queryList() {

        return this.doQuery();
    }

    @Override
    public List<SysMenu> getByRoleId(String roleId) {

        return this.doGetByRoleId(roleId);
    }

    @Cacheable(value = SysMenu.CACHE_NAME, key = "#id", unless = "#result == null")
    @Override
    public SysMenu findById(@NonNull String id) {

        return this.doGetById(id);
    }

    @OpLog(type = OpLogType.SYS, name = "新增菜单，ID：{}, 编号：{}", params = {"#id", "#code"})
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String create(@NonNull CreateSysMenuVo vo) {

        SysMenu data = this.doCreate(vo);

        OpLogUtil.setVariable("id", data.getId());
        OpLogUtil.setVariable("code", vo.getCode());
        OpLogUtil.setExtra(vo);

        return data.getId();
    }

    @OpLog(type = OpLogType.SYS, name = "修改菜单，ID：{}, 编号：{}", params = {"#id", "#code"})
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void update(@NonNull UpdateSysMenuVo vo) {

        SysMenu oriMenu = this.findById(vo.getId());

        if (!ObjectUtil.equals(vo.getDisplay(), oriMenu.getDisplay().getCode())) {
            throw new DefaultClientException("菜单【" + oriMenu.getTitle() + "】" + "不允许更改类型！");
        }

        SysMenu data = this.doUpdate(vo);

        OpLogUtil.setVariable("id", data.getId());
        OpLogUtil.setVariable("code", vo.getCode());
        OpLogUtil.setExtra(vo);
    }

    @OpLog(type = OpLogType.SYS, name = "删除菜单，ID：{}", params = "#id")
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteById(@NonNull String id) {

        SysMenu oriMenu = this.findById(id);

        List<SysMenu> children = this.doGetChildrenById(id);
        if (CollectionUtil.isNotEmpty(children)) {
            //如果子节点不为空
            throw new DefaultClientException("菜单【" + oriMenu.getTitle() + "】存在子菜单，无法删除！");
        }

        this.doDeleteById(id);
    }

    @Override
    public List<SysMenu> selector(SysMenuSelectorVo vo) {

        return this.doSelector(vo);
    }

    @OpLog(type = OpLogType.SYS, name = "启用菜单，ID：{}", params = "#ids", loopFormat = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchEnable(@NonNull List<String> ids, @NonNull String userId) {

        if (CollectionUtil.isEmpty(ids)) {
            return;
        }

        this.doBatchEnable(ids, userId);
    }

    @OpLog(type = OpLogType.SYS, name = "停用菜单，ID：{}", params = "#ids", loopFormat = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchUnable(@NonNull List<String> ids, @NonNull String userId) {

        if (CollectionUtil.isEmpty(ids)) {
            return;
        }

        this.doBatchUnable(ids, userId);
    }

    @Override
    public Boolean existPermission(String permission) {
        Wrapper<SysMenu> queryWrapper = Wrappers.lambdaQuery(SysMenu.class)
                .eq(SysMenu::getPermission, permission);
        return this.count(queryWrapper) > 0;
    }

    protected List<SysMenu> doQuery() {

        return getBaseMapper().query();
    }

    protected List<SysMenu> doGetByRoleId(String roleId) {

        return getBaseMapper().getByRoleId(roleId);
    }

    protected SysMenu doGetById(@NonNull String id) {

        return getBaseMapper().findById(id);
    }

    protected SysMenu doCreate(@NonNull CreateSysMenuVo vo) {

        SysMenu data = new SysMenu();

        data.setId(IdUtil.getId());
        this.setDataForCreate(vo, data);

        getBaseMapper().insert(data);

        return data;
    }

    protected SysMenu doUpdate(@NonNull UpdateSysMenuVo vo) {

        SysMenu data = new SysMenu();

        data.setId(vo.getId());

        this.setDataForCreate(vo, data);

        data.setAvailable(vo.getAvailable());

        SysMenuService thisService = getThis(this.getClass());

        SysMenu record = thisService.findById(vo.getId());

        data.setIsSpecial(record.getIsSpecial());

        getBaseMapper().deleteById(vo.getId());

        getBaseMapper().insert(data);

        return data;
    }

    protected void doDeleteById(@NonNull String id) {

        getBaseMapper().deleteById(id);
    }

    protected List<SysMenu> doSelector(SysMenuSelectorVo vo) {

        return getBaseMapper().selector(vo);
    }

    protected void doBatchEnable(@NonNull List<String> ids, @NonNull String userId) {

        Wrapper<SysMenu> wrapper = Wrappers.lambdaUpdate(SysMenu.class)
                .set(SysMenu::getAvailable, Boolean.TRUE).in(SysMenu::getId, ids);
        getBaseMapper().update(new SysMenu(), wrapper);
    }

    protected void doBatchUnable(@NonNull List<String> ids, @NonNull String userId) {

        Wrapper<SysMenu> wrapper = Wrappers.lambdaUpdate(SysMenu.class)
                .set(SysMenu::getAvailable, Boolean.FALSE).in(SysMenu::getId, ids);
        getBaseMapper().update(new SysMenu(), wrapper);
    }

    protected void setDataForCreate(@NonNull CreateSysMenuVo vo, @NonNull SysMenu data) {

        SysMenuDisplay sysMenuDisplay = EnumUtil.getByCode(SysMenuDisplay.class, vo.getDisplay());

        SysMenu parentMenu = null;
        if (!StringUtil.isBlank(vo.getParentId())) {
            parentMenu = this.findById(vo.getParentId());
            if (parentMenu == null) {
                throw new DefaultClientException("父级菜单不存在！");
            }
            if (parentMenu.getId().equals(data.getId())) {
                throw new DefaultClientException("父级菜单不能是当前菜单！");
            }
        }
        data.setCode(vo.getCode());
        data.setTitle(vo.getTitle());
        data.setParentId(parentMenu == null ? StringPool.EMPTY_STR : parentMenu.getId());
        data.setDisplay(sysMenuDisplay);
        data.setDescription(
                StringUtil.isBlank(vo.getDescription()) ? StringPool.EMPTY_STR : vo.getDescription());

        if (sysMenuDisplay == SysMenuDisplay.CATALOG || sysMenuDisplay == SysMenuDisplay.FUNCTION) {
            if (parentMenu != null) {
                //父级菜单必须是目录
                if (parentMenu.getDisplay() != SysMenuDisplay.CATALOG) {
                    throw new DefaultClientException(
                            "父级菜单类型必须是【" + SysMenuDisplay.CATALOG.getDesc() + "】！");
                }
            }

            data.setName(vo.getName());
            data.setIcon(vo.getIcon());
            data.setPath(vo.getPath());
            data.setHidden(vo.getHidden());

            if (sysMenuDisplay == SysMenuDisplay.FUNCTION) {
                // 功能必须有parentId
                if (parentMenu == null) {
                    throw new DefaultClientException(
                            "此菜单类型是【" + SysMenuDisplay.FUNCTION.getDesc() + "】，父级菜单不能为空！");
                }
                data.setComponent(vo.getComponent());
                data.setComponentType(
                        EnumUtil.getByCode(SysMenuComponentType.class, vo.getComponentType()));
                data.setNoCache(vo.getNoCache());

                if (SecurityConstants.PERMISSION_ADMIN_NAME.equals(vo.getPermission())) {
                    throw new DefaultClientException(
                            "权限【" + SecurityConstants.PERMISSION_ADMIN_NAME + "】为内置权限，请修改！");
                }

                data.setPermission(vo.getPermission());
            }
        } else if (sysMenuDisplay == SysMenuDisplay.PERMISSION) {

            if (parentMenu != null) {
                //父级菜单必须是目录
                if (parentMenu.getDisplay() != SysMenuDisplay.FUNCTION) {
                    throw new DefaultClientException(
                            "父级菜单类型必须是【" + SysMenuDisplay.FUNCTION.getDesc() + "】！");
                }
            } else {
                throw new DefaultClientException(
                        "此菜单类型是【" + SysMenuDisplay.PERMISSION.getDesc() + "】，父级菜单不能为空！");
            }

            if (SecurityConstants.PERMISSION_ADMIN_NAME.equals(vo.getPermission())) {
                throw new DefaultClientException(
                        "权限【" + SecurityConstants.PERMISSION_ADMIN_NAME + "】为内置权限，请修改！");
            }

            data.setPermission(vo.getPermission());
        }
    }

    protected List<SysMenu> doGetChildrenById(String id) {

        return getBaseMapper().getChildrenById(id);
    }

    @Override
    public List<MenuDto> getMenuByUserId(String userId, boolean isAdmin) {

        List<MenuDto> menus = this.doGetMenus(userId, isAdmin);

        List<String> collectionMenuIds = this.doGetCollectMenuIds(userId);

        if (!CollectionUtil.isEmpty(menus)) {
            // 用env渲染${xxx}属性
            menus.forEach(menu -> {
                menu.setPath(ApplicationUtil.resolvePlaceholders(menu.getPath()));
            });

            // 渲染spel表达式
            Map<String, Object> vars = getDefaultVars();
            menus.stream().filter(menu -> this.hasSpecExpression(menu.getPath())).forEach(menu -> {
                List<String> expressions = this.getAllExpressions(menu.getPath());
                if (!CollectionUtil.isEmpty(expressions)) {
                    String oriPath = menu.getPath();
                    for (String expression : expressions) {
                        Object parsed = SpelUtil.parse(expression.replaceAll("\\{", "").replaceAll("}", ""),
                                vars);
                        oriPath = oriPath.replace(expression, parsed == null ? "" : String.valueOf(parsed));
                    }

                    menu.setPath(oriPath);
                }
            });

            if (!CollectionUtil.isEmpty(collectionMenuIds)) {
                menus.forEach(menu -> {
                    menu.setIsCollect(collectionMenuIds.contains(menu.getId()));
                });
            }
        }

        return menus;
    }

    @Override
    public Set<String> getPermissionsByUserId(String userId, boolean isAdmin) {

        return getBaseMapper().getPermissionsByUserId(userId, isAdmin);
    }

    @Override
    public Set<String> getRolePermissionByUserId(String userId) {
        return getBaseMapper().getRolePermissionByUserId(userId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void collect(String userId, String menuId) {

        if (StringUtil.isBlank(userId) || StringUtil.isBlank(menuId)) {
            return;
        }

        this.cancelCollect(userId, menuId);

        getBaseMapper().collectMenu(IdUtil.getId(), userId, menuId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void cancelCollect(String userId, String menuId) {

        if (StringUtil.isBlank(userId) || StringUtil.isBlank(menuId)) {
            return;
        }

        getBaseMapper().cancelCollectMenu(userId, menuId);
    }

    private List<String> getAllExpressions(String s) {

        if (!this.hasSpecExpression(s)) {
            return null;
        }

        List<String> results = new ArrayList<>();
        String[] arr = s.split("#\\{");
        for (int i = 1; i < arr.length; i++) {
            if (!arr[i].contains("}")) {
                continue;
            }
            results.add("#{" + arr[i].substring(0, arr[i].indexOf("}")) + "}");
        }

        return results;
    }

    private boolean hasSpecExpression(String s) {

        return RegUtil.isMatch(Pattern.compile("^.*#\\{.*}.*$"), s);
    }

    protected Map<String, Object> getDefaultVars() {

        Map<String, Object> vars = new HashMap<>();

        vars.put("_token", userTokenResolver.getToken());
        vars.put("_fullToken", userTokenResolver.getFullToken());
        vars.put("_tokenKey", tokenKey);

        return vars;
    }

    protected List<MenuDto> doGetMenus(String userId, boolean isAdmin) {

        return getBaseMapper().getMenuByUserId(userId, isAdmin);
    }

    protected List<String> doGetCollectMenuIds(String userId) {

        return getBaseMapper().getCollectMenuIds(userId);
    }

    @CacheEvict(value = SysMenu.CACHE_NAME, key = "#key")
    @Override
    public void cleanCacheByKey(Serializable key) {

    }
}
